//
// Created by HeiBai on 2020/6/13.
//

#include "MyPlayer.h"
#include "macro.h"

void *customTaskStartThread(void *pVoid) {
    MyPlayer *myPlayer = static_cast<MyPlayer *>(pVoid);
    myPlayer->start_();
    return 0;
}

void *customTaskPrepareThread(void *pVoid) {
    MyPlayer *myPlayer = static_cast<MyPlayer *>(pVoid);
    //创建异步函数
    myPlayer->prepare_();
    return 0;
}

MyPlayer::MyPlayer() {}

MyPlayer::~MyPlayer() {
    if (this->data_source) {
        delete this->data_source;
        this->data_source = 0;
    }
}

MyPlayer::MyPlayer(const char *data_source, JNICallback *pCallback) {
    //拿到的是悬空
    //this->data_source = data_source;
    //长度不对,需要加1  c和C++的区别
    this->data_source = new char[strlen(data_source) + 1];
    strcpy(this->data_source, data_source);
    this->jniCallback = pCallback;
}

//准备工作 ，（拆包裹 解码 解封装 解复用）（音频流 视频流 字幕流。。。等） 主线程
void MyPlayer::prepare() {
    //创建异步线程
    pthread_create(&this->pid_prepare, 0, customTaskPrepareThread, this);
}

/**
 * 异步函数，拆包裹
 */
void MyPlayer::prepare_() {
    this->avFormatContext = avformat_alloc_context();


    //此字典 能决定打开的需求
    AVDictionary *dictionary = 0;
    //打开文件花的时长 单位是微秒
    av_dict_set(&dictionary, "timeout", "5000000", 0);
   // av_register_all();//
    // 初始化网络
    avformat_network_init();
    /**
     * 媒体上下文
     * 数据源
     * 数据格式
     * 数据字典
     *  判断是不是包裹，如果是石头（被损坏的数据），就无法解析
     */
    int ret = avformat_open_input(&avFormatContext, this->data_source, 0, &dictionary);
    __android_log_print(ANDROID_LOG_DEBUG, "MyPlayer===========",
                        "prepare_avformat_open_input_____:%s", av_err2str(ret));

    av_dict_free(&dictionary); // 释放字典
    //返回 0是成功
    if (ret) {
        // TODO xia chen hui 2020/6/13 19:36 写JNI回调，通知java层，数据流被损坏了
        if (jniCallback) {
            this->jniCallback->onErrorAction(THREAD_CHILD, FFMPEG_CAN_NOT_OPEN_URL);
        }
        return;
    }
    //1.寻找媒体格式中的（音频 视频 字幕） ，不给字典是因为不需要设置额外的配置  2.给媒体上下文赋值
    ret = avformat_find_stream_info(this->avFormatContext, 0);
    if (ret < 0) {
        // TODO xia chen hui 2020/6/13 19:36 写JNI回调，通知java层，找不到流媒体
        if (jniCallback) {
            this->jniCallback->onErrorAction(THREAD_CHILD, FFMPEG_CAN_NOT_FIND_STREAMS);
        }
        return;
    }
    //可以进行解析了
    //循环遍历 ，媒体格式里的流    流0（视频） 流1 （音频） 流2（字幕） 等等

    for (int i = 0; i < this->avFormatContext->nb_streams; ++i) {
        //获取媒体流
        AVStream *stream = this->avFormatContext->streams[i];
        //获取编解码器
        AVCodecParameters *codecParameters = stream->codecpar;
        //编解码器ID
        enum AVCodecID codecId = codecParameters->codec_id;
        //拿到编解码器
        AVCodec *codec = avcodec_find_decoder(codecId);
        if (!codec) {
            // TODO xia chen hui 2020/6/13 19:36 写JNI回调，通知java层  找不到解码器
            if (jniCallback) {
                this->jniCallback->onErrorAction(THREAD_CHILD, FFMPEG_FIND_DECODER_FAIL);
            }
            return;
        }
        //为了解码，需要解码器上下文h
        AVCodecContext *codecContext = avcodec_alloc_context3(codec);
        if (!codecContext) {
            // TODO xia chen hui 2020/6/13 19:36 写JNI回调，通知java层， 获取解码器上下文失败
            if (jniCallback) {
                this->jniCallback->onErrorAction(THREAD_CHILD, FFMPEG_ALLOC_CODEC_CONTEXT_FAIL);
            }
            return;
        }
        //解码器上下文 设置参数
        ret = avcodec_parameters_to_context(codecContext, codecParameters);
        if (ret < 0) {
            // TODO xia chen hui 2020/6/13 19:36 写JNI回调，通知java层， 解码器上下文设置失败
            if (jniCallback) {
                this->jniCallback->onErrorAction(THREAD_CHILD,
                                                 FFMPEG_CODEC_CONTEXT_PARAMETERS_FAIL);
            }
            return;
        }
        //打开解码器
        ret = avcodec_open2(codecContext, codec, 0);
        if (ret) {
            //0是成功，非0为true
            // TODO xia chen hui 2020/6/13 19:36 写JNI回调，通知java层， 打开解码器失败
            if (jniCallback) {
                this->jniCallback->onErrorAction(THREAD_CHILD, FFMPEG_OPEN_DECODER_FAIL);
            }
            return;
        }
        //使用解码器
        //区分音频 视频 通道
        if (codecParameters->codec_type == AVMEDIA_TYPE_AUDIO) {
            this->audioChannel = new AudioChannel(i, codecContext);
        } else if (codecParameters->codec_type == AVMEDIA_TYPE_VIDEO) {
            //现在很多字幕流放在视频轨里一起了。
            this->videoChannel = new VideoChannel(i, codecContext);
            videoChannel->setRenderCallback(renderCallback);
        } else {
            // TODO xia chen hui 2020/6/13 19:36 写JNI回调，通知java层， 没有音视频
            if (jniCallback) {
                this->jniCallback->onErrorAction(THREAD_CHILD, FFMPEG_NOMEDIA);
            }
        }


    }
    if (!audioChannel && !videoChannel) {
        // 把错误信息，告诉给Java层去（回调给Java）
        this->jniCallback->onErrorAction(THREAD_CHILD, FFMPEG_NOMEDIA);
        return;
    }
    //告诉java层 ，准备OK了
    if (jniCallback) {
        this->jniCallback->onPrepared(THREAD_CHILD);
    }
}

void MyPlayer::start() {
    //开始播放   需要异步线程

    //让音频 视频两个通道运行起来
    isPlayer = 1;//是否运行的标记 非0为true
    if (this->videoChannel) {
        this->videoChannel->start();
    }
    if (this->audioChannel) {
        this->audioChannel->start();
    }
    //开线程  读包，把未解码的流数据 放入队列

    pthread_create(&pid_start, 0, customTaskStartThread, this);
}

/**
 * 异步函数， 读包
 */
void MyPlayer::start_() {
    while (this->isPlayer) {
        // 内存泄漏点1，解决方案：控制队列大小
        if (videoChannel && videoChannel->packets.queueSize() > 100) {
            // 休眠 等待队列中的数据被消费
            av_usleep(10 * 1000);
            continue;
        }
        // 内存泄漏点1，解决方案：控制队列大小
        if (audioChannel && audioChannel->packets.queueSize() > 100) {
            // 休眠 等待队列中的数据被消费
            av_usleep(10 * 1000);
            continue;
        }


        //未解码的格式，保存在AVPacket中
        AVPacket *packet = av_packet_alloc();
        //执行这个 ，packet就可以获取值了
        int ret = av_read_frame(this->avFormatContext, packet);
        if (!ret) {  //ret  == 0
            //把已经得到的Packet 放入队列中
            //先要判断是否是音频 还是视频
            if (videoChannel && videoChannel->stream_index == packet->stream_index) {
                // 说明是视频 ，
                this->videoChannel->packets.push(packet);
            } else if (audioChannel && audioChannel->stream_index == packet->stream_index) {
                //音频
                this->audioChannel->packets.push(packet);
            }
        } else if (ret == AVERROR_EOF) {
            //文件末尾 ，流读完了

        } else {
            //代表失败了
            break;
        }
    }

    //释放
    isPlayer = 0;
    videoChannel->stop();
    audioChannel->stop();
}

void MyPlayer::setRenderCallback(RenderCallback renderCallback) {
    this->renderCallback = renderCallback;
}

